import java.awt.Color;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

/**
 * Manages interactions between a GameView and a GameModel. Listens to events from
 * both and makes the appropriate communications between them.
 * 
 * @author Sam Snyder
 *
 */
public class JottoController implements GameController {
	
	/**
	 * The maximum length of a word in Jotto.
	 */
	public static final int WORDLENGTH = 5;
	
	/**
	 * The view responsible for presenting the game to the user.
	 */
	private GameView view;
	
	/**
	 * The object responsible for representing the game's data.
	 */
	private GameModel model;
	
	/**
	 * Represents the phase of the Jotto game.
	 */
	private JottoPhase phase;
	
	/**
	 * Keeps track of whose turn it is.
	 */
	private PlayerSelect turn;
	
	/**
	 * Saves references to the model and view, registers event listeners with them.   
	 * 
	 * @param v the view being observed by the GameController
	 * @param m the model being manipulated by the GameController
	 */
	public JottoController(GameView v, GameModel m) {
		this.view = v;
		this.model = m;
		this.phase = JottoPhase.BEGIN;
		this.turn = PlayerSelect.P1;
		
		view.addGuessListener(new GuessListener());
		view.addSecretListener(PlayerSelect.P1, new SecretListener(PlayerSelect.P1));
		view.addSecretListener(PlayerSelect.P2, new SecretListener(PlayerSelect.P2));
	}
	
	/**
	 * Resets the game to its initial state, except for score and wins information.
	 */
	public void newGame() {
		phase = JottoPhase.BEGIN;
		view.setGuessEditable(false);
		view.setSecretEditable(PlayerSelect.P1, true);
		view.setSecretEditable(PlayerSelect.P2, true);
		view.setSecretText(PlayerSelect.P1, "");
		view.setSecretText(PlayerSelect.P2, "");
		view.setGuessLabel("Player1: ", Color.BLUE);
		model.newGame();
	}
	
	/**
	 * Assigns players scores and wins and updates the UI.
	 */
	public void concludeGame() {
		// Both players have guessed the word / run out of guesses
		PlayerSelect winner = model.concludeGame();
		view.setScore(PlayerSelect.P1, 
				model.getScore(PlayerSelect.P1));
		view.setScore(PlayerSelect.P2, 
				model.getScore(PlayerSelect.P2));
		view.setWins(PlayerSelect.P1, 
				model.getWins(PlayerSelect.P1));
		view.setWins(PlayerSelect.P2, 
				model.getWins(PlayerSelect.P2));
		if (winner == null) {
			view.setUserMessage("Players tied. You both win.");
		} else if (winner == PlayerSelect.P1) {
			view.setUserMessage("Player1 is VICTORIOUS! Play again?");
		} else {
			view.setUserMessage("Player2 is VICTORIOUS! Play again?");
		}
	}
	
	
	/**
	 * Responsible for responding to events related to changes in secret word entry.
	 *
	 */
	private class SecretListener implements KeyListener {
		/**
		 * Specifies which player this is listening to events from.
		 */
		private PlayerSelect p;
		
		/**
		 * @param player the player to listen to events form
		 */
		SecretListener(PlayerSelect player) {
			this.p = player;
		}
		
		/**
		 * Does nothing.
		 * @param e unused.
		 */
		@Override
		public void keyPressed(KeyEvent e) {
			// TODO Auto-generated method stub
			
		}

		/**
		 * Does nothing.
		 * @param e unused.
		 */
		@Override
		public void keyReleased(KeyEvent e) {

		}

		/**
		 * When a key is typed in a secret box force it to conform to input rules:
		 * Must be alphabetical, must be lowercase, no more than WORDLENGTH letters.
		 * If there are WORDLENGTH letters then see if the word is in the dictionary.
		 * If it is in the dictionary then use it as the player's word. If it is not
		 * then display an error message to the user and clear the entry box. 
		 * 
		 * @param e the event fired.
		 */
		@Override
		public void keyTyped(KeyEvent e) {
			String keychar = "" + e.getKeyChar();
		

			// Alphabetical characters only
			if (keychar.matches("[^a-zA-Z]")) {
				if (e.getKeyChar() != '\b') {
					view.setUserMessage("No non-alphabet characters allowed");
				}
				e.setKeyChar('\u0000');
			} 

			// Lowercase only
			e.setKeyChar(Character.toLowerCase(e.getKeyChar()));
			
			String secret = view.getSecretText(p) + e.getKeyChar();
			
			// If the appropriate length then
			if (secret.length() == WORDLENGTH) {
				if (model.wordValid(secret)) {
					try {
						model.setSecretWord(p, secret);
					} catch (WordException e1) {
						view.setUserMessage(e1.getOutput());
					}
					view.setSecretText(p, secret);
					view.setSecretEditable(p, false);
					switch(phase) {
					case BEGIN:
						phase = JottoPhase.SECRETENTERED;
						view.setUserMessage("Other player enter word.");
						break;
					case SECRETENTERED:
						view.setGuessEditable(true);
						phase = JottoPhase.BOTHPLAYING;
						view.clearGuessHistories(); // If left over from previous games
						break;
					default:
					}
				} else {
					view.setUserMessage("Word not in the dictionary.");
				}
			} else if (secret.length() > WORDLENGTH) {
				// Truncate anything past 5 letters
				view.setUserMessage("Words must be exactly " + WORDLENGTH 
						+ " letters long");
				e.setKeyChar('\u0000');
			}
		}
	}
	
	/**
	 * Responsible for responding to events related to changes in the guess box, which
	 * is pretty much the entire game.
	 *
	 */
	private class GuessListener implements KeyListener {

		/**
		 * Does nothing.
		 * @param e unused.
		 */
		@Override
		public void keyPressed(KeyEvent e) {
			// TODO Auto-generated method stub
			
		}

		/**
		 * Does nothing.
		 * @param e unused.
		 */
		@Override
		public void keyReleased(KeyEvent e) {
			// TODO Auto-generated method stub
			
		}

		/**
		 * When a key is pressed in the guessBox force it to conform to input rules:
		 * all letters converted to lowercase, when five letters are entered check that
		 * they are valid and either display a problem message to the user or commit the
		 * word to the model and swtich which player the field corresponds to in the view.
		 * 
		 * @param e the event fired.
		 */
		public void keyTyped(KeyEvent e) {
			String keychar = "" + e.getKeyChar();
			
			// Alphabetical characters only
			if (keychar.matches("[^a-zA-Z]")) {			
				if (e.getKeyChar() != '\b') {
					view.setUserMessage("No non-alphabet characters allowed");
				}
				e.setKeyChar('\u0000');
			} 

			// Lowercase only
			e.setKeyChar(Character.toLowerCase(e.getKeyChar()));
			String guess = view.getGuessText() + e.getKeyChar();
			
			// Formatting rules met, use the word if appropriate length
			if (guess.length() == WORDLENGTH) {
				// If the appropriate length then
				if (model.wordValid(guess)) {
					try {
						e.setKeyChar('\u0000');
						view.setUserMessage("");
						int matched = 0;
						
						// Make the guess
						matched = model.guess(turn.other(), guess);
						view.addGuessHistory(turn, guess, matched);
						
						// Update UI with guess result and advance phase if necessary
						view.setGuessText(""); // Empty guess box
						switch(phase) {
						case BOTHPLAYING:		
							if (model.lastPerfectlyMatched()) {
								// A guess was made successfully 
								view.setUserMessage(turn.toString() + " has guessed " 
										+ turn.other().toString() + "'s word");

								phase = JottoPhase.PLAYERFINISHED;
							} else if (model.guessLimitReached(turn)) {
								// No successful guess, but ran out of guesses
								view.setUserMessage(turn.toString() 
										+ "has run out of guesses");
								phase = JottoPhase.PLAYERFINISHED;
							} 
							// Change to the other player's turn
							view.setGuessLabel(turn.other().toString() + ": ", 
									turn.other().color());
							turn = turn.other(); 
							break;
						case PLAYERFINISHED:
							// Both players have finished guessing
							if (model.lastPerfectlyMatched()) {
								// The last guess matched
								concludeGame();
								newGame();
							} else if (model.guessLimitReached(turn)) {
								// Ran out of guesses without getting it
								view.setUserMessage(turn.toString() 
										+ "has run out of guesses.");
								concludeGame();
								newGame();
							}
							break;
						default:	
						}
					} catch (WordException e1) {
						// This will never happen if this controller and the model
						// have the same definition of a valid word.
						view.setUserMessage(e1.getOutput());
					}
				} else {
					// Word is not in the dictionary, don't accept the input
					view.setUserMessage("Word not in the dictionary.");
				}
			} else if (guess.length() > WORDLENGTH) {
				// Truncate anything past 5 letters
				e.setKeyChar('\u0000');
				view.setUserMessage("Words must be exactly " + WORDLENGTH 
						+ " letters long");
			}	
		}
	}
	 
	/**
	 * Enumerated type used to represent the different phases a game of Jotto can be in.
	 *
	 */
	private static enum JottoPhase {
		
		/**
		 * A fresh game has just begun, players have yet to enter their secret words.
		 */
		BEGIN, 
		
		/**
		 * A player has entered a secret, the other has not.
		 */
		SECRETENTERED,
		
		/**
		 * Players are taking turns making guesses.
		 */
		BOTHPLAYING,
		
		/**
		 * One player has finished guessing, the other has not.
		 */
		PLAYERFINISHED,
	}
}
